/* kernel/vsprintf.c 
 * 
 * Copyright (C) 2021, 2022 intirain. 
 *
 * This file is part of evx. 
 * 
 * evx is free software: you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License as published by 
 * the Free Software Foundation, either version 3 of the License, or 
 * (at your option) any later version. 
 * 
 * evx is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
 * GNU General Public License for more details. 
 * 
 * You should have received a copy of the GNU General Public License 
 * along with evx. If not, see <https://www.gnu.org/licenses/>
 */ 




#include <kernel/init.h>
#include <kernel/module.h>
#include <kernel/kernel.h>

#include <arch/string.h>
#include <arch/archinfo.h>

#include <sys/types.h>
#include <stdarg.h>


/* vsprintf() are almost copied from linux, but ... well, I have no 
 * words to say. 
 *
 * and you should know, we removed a lot of useless things (useful in 
 * userspace) in kernel. 
 * i don't know why but va_arg can only get first 6 arguments on x86_64 
 * and 6 is also the number of arguments store in registers. I'm trying 
 * i386 */


static const char digits[] = "0123456789abcdef";

#define ZERO		1
#define UNSIGNED 	2

static int number(char *buf, long x, int base, int flags, int width) {
	int neg = 0, i = 0, j;
	char t[PTRSIZE * 2];
	unsigned long nr;

	memset(t, 0, PTRSIZE * 2);

	if (!(flags & UNSIGNED) && x < 0) {
		nr = -x;
		neg = 1;
	}

	else 
		nr = x;

	if (!nr) 
		t[i++] = '0';

	else while (nr) {
		t[i++] = digits[nr % base];

		nr /= base;
	}

	if (neg) 
		t[i++] = '-';
	
	if (flags & ZERO) {
		if (width < 0) 
			width = PTRSIZE * 2;

		for (j = width - 1; j; j--) {
			if (t[j]) 
				break;
			
			t[j] = '0';
		}

		i = PTRSIZE * 2;
	}

	/* t[i] is always 0 */
	j = i;

	while (--j >= 0) 
		buf[i - j - 1] = t[j];

	return i;
}

/* the function used to format a string, using stdargs */
#define output(c) 	buf[bufindex++] = c

int vsprintf(char *buf, const char *s, va_list args) {
	int i, bufindex, flag;
	char *t, *fmt;
       
	bufindex = 0;

	for (i = 0, fmt = (char *) s; *fmt; fmt++, i++) {
		if (likely(*fmt != '%')) {
			output(*fmt);

			continue;
		}

		/* skip '%' */
		fmt++;

		if (*fmt == '0') {
			flag |= ZERO;

			fmt++;
		}

		switch (*fmt) {
		case 'c':
			output(va_arg(args, int));

			break;

		case 's':
			t = va_arg(args, char *);
			
			while (*t) 
				output(*t++);

			break;

		case 'd':
			bufindex += number(buf + bufindex, va_arg(args, int), 10, flag, -1);

			break;

		case 'x':
		case 'p':
			/* ignore upcase */

			bufindex += number(buf + bufindex, va_arg(args, int), 16, 
					flag | UNSIGNED | ZERO, PTRSIZE * 2);

			break;

		case '%':
			output('%');

			break;

		default:
			output('%');
			output(*fmt);

			(void) va_arg(args, int);

			break;
		}
	}

	return bufindex;
}

